package com.jinyang.plugindemo.hook.instrumentation


import android.app.Activity
import android.app.Instrumentation
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Bundle
import android.os.IBinder
import android.os.PersistableBundle
import androidx.fragment.app.Fragment
import com.jinyang.plugindemo.log

class InstrumentationProxy(
    var realContext: Context,
    var base: Instrumentation,
    var context: ContextWrapper
) : Instrumentation() {
    private val KEY_COMPONENT = "commontec_component"

    companion object {
        /**
         * hook 系统，替换 Instrumentation 为我们自己的 InstrumentationProxy
         */
        fun inject(activity: Activity, context: ContextWrapper) {
            // Reflect 是从 VirtualApp 里拷贝的反射工具类，使用很流畅~
            val reflect = Reflect.on(activity)
            val activityThread = reflect.get<Any>("mMainThread")
            val base = Reflect.on(activityThread).get<Instrumentation>("mInstrumentation")
            val mInstrumentation = InstrumentationProxy(activity, base, context)
            Reflect.on(activityThread).set("mInstrumentation", mInstrumentation)
            Reflect.on(activity).set("mInstrumentation", mInstrumentation)
        }
    }

    /**
     * newActivity 是创建 Activity 实例，这里要返回真正需要运行的插件 Activity，
     * 这样后面系统就会基于这个 Activity 实例来进行对应的生命周期的调用。
     */
    override fun newActivity(cl: ClassLoader, className: String, intent: Intent): Activity? {
        val componentName = intent.getParcelableExtra<ComponentName>(KEY_COMPONENT)
        var clazz = context.classLoader.loadClass(componentName?.className)
        intent.component = componentName
        return clazz.newInstance() as Activity?
    }

    /**
     *  hook 系统的资源处理方式: 生成 Resources 以后，直接反射替换掉 Activity 中的 mResource 变量即可
     */
    private fun injectActivity(activity: Activity?) {
        val intent = activity?.intent
        val base = activity?.baseContext
        try {
            //反射替换 mResources 资源
            Reflect.on(base).set("mResources", context.resources)
            Reflect.on(activity).set("mResources", context.resources)
            Reflect.on(activity).set("mBase", context)
            Reflect.on(activity).set("mApplication", context.applicationContext)
            // for native activity
            val componentName: ComponentName? =
                intent!!.getParcelableExtra<ComponentName>(KEY_COMPONENT)
            val wrapperIntent = Intent(intent)
            wrapperIntent.setClassName(componentName?.packageName!!, componentName.className)
            activity.intent = wrapperIntent

        } catch (e: Exception) {
        }
    }

    override fun callActivityOnCreate(activity: Activity?, icicle: Bundle?) {
        injectActivity(activity)
        super.callActivityOnCreate(activity, icicle)
    }

    override fun callActivityOnCreate(
        activity: Activity?,
        icicle: Bundle?,
        persistentState: PersistableBundle?
    ) {
        injectActivity(activity)
        super.callActivityOnCreate(activity, icicle, persistentState)
    }

    /**
     *  替换 intent 中的类名为占位 Activity 的类名，这样系统在 Manifest 中查找的时候就可以找到 Activity
     */
    private fun injectIntent(intent: Intent?) {
        var component: ComponentName? = null
        var oldComponent = intent?.component
        if (component == null || component.packageName == realContext.packageName) {
            component = ComponentName(
                "com.jinyang.plugindemo",
                "com.jinyang.plugindemo.hook.StubHookActivity"
            )
            intent?.component = component
            intent?.putExtra(KEY_COMPONENT, oldComponent)
        }
    }

    /**
     * execStartActivity 是在启动 Activity 的时候必经的一个过程，这时还没有到达 AMS，
     * 所以，在这里把 Activity 替换成宿主中已经注册的 StubActivity，
     * 这样 AMS 在检测 Activity 的时候就认为已经注册过了
     */
    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: Activity,
        intent: Intent,
        requestCode: Int
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call("execStartActivity", who, contextThread, token, target, intent, requestCode).get()
    }

    fun execStartActivity(
        who: Context?,
        contextThread: IBinder?,
        token: IBinder?,
        target: Activity?,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }

    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: Fragment,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }

    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: String,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }
}
